#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
交互式方程化简 / 求解器
Python 3.x  +  SymPy  +  Tkinter

配置区：前三行就是全部可调参数
"""
ESCAPE_OPEN  = '`'      # 多字母符号的左定界符
ESCAPE_CLOSE = '`'      # 多字母符号的右定界符
GEOMETRY     = "700x600"  # 主窗口初始大小

# ------------- 以下代码基本不用动 ------------- #
import tkinter as tk
from tkinter import ttk, messagebox
import re
import sympy as sp
from sympy.parsing.sympy_parser import (
    parse_expr, standard_transformations,
    implicit_multiplication_application
)

TRANSFORMS = standard_transformations + (implicit_multiplication_application,)

# ---------- 解析器：支持转义多字母符号 ---------- #
TOKEN_RE = re.compile(
    re.escape(ESCAPE_OPEN) + r'(.*?)' + re.escape(ESCAPE_CLOSE)
)

def parse_expr_safe(text: str):
    """把字符串安全地解析成 SymPy 表达式，支持转义多字母符号"""
    sym_map, counter = {}, 0
    def replacer(m):
        nonlocal counter
        literal = m.group(1).strip()
        if not literal:
            raise ValueError("空转义符号")
        tmp = f"__SYM{counter}__"
        sym_map[tmp] = literal
        counter += 1
        return tmp
    safe_text = TOKEN_RE.sub(replacer, text)
    try:
        return parse_expr(
            safe_text,
            transformations=TRANSFORMS,
            local_dict={tmp: sp.Symbol(name) for tmp, name in sym_map.items()}
        )
    except Exception as e:
        raise ValueError(f"表达式解析失败: {e}")

# ---------- 核心逻辑：消除中间变量 ---------- #
def eliminate_intermediates(equations, targets, allowed_syms):
    to_eliminate = list(
        set().union(*(eq.free_symbols for eq in equations)) -
        allowed_syms -
        set(targets)
    )
    if not to_eliminate:
        return equations

    eqs = list(equations)
    for var in to_eliminate:
        solver_eq = None
        for idx, eq in enumerate(eqs):
            if eq.lhs == var and var not in eq.rhs.free_symbols:
                solver_eq, solver_idx = eq, idx
                break
        if solver_eq is None:
            continue
        subs_expr = solver_eq.rhs
        del eqs[solver_idx]
        eqs = [eq.subs(var, subs_expr) for eq in eqs]
    return eqs

def solve_all(equations, targets, allowed_syms):
    simplified = eliminate_intermediates(equations, targets, allowed_syms)
    try:
        sol = sp.solve(simplified, targets, dict=True)
    except Exception as e:
        raise RuntimeError(f"求解失败: {e}")

    if not sol:
        return True, ["无满足条件的解"]

    remaining = set()
    for s in sol:
        remaining.update(*[expr.free_symbols for expr in s.values()])
    remaining -= allowed_syms
    success = len(remaining) == 0
    lines = []
    for idx, single_sol in enumerate(sol, 1):
        parts = [
            f"{t} = {sp.simplify(single_sol.get(t, sp.Symbol('未找到')))}"
            for t in targets
        ]
        lines.append(f"解 {idx}: " + ",  ".join(parts))
    if not success:
        lines.append(f"含未允许的符号 {remaining}")
    return success, lines

# ---------- GUI ---------- #
class EquationSolverGUI:
    def __init__(self, root):
        self.root = root
        root.title("交互式方程化简 / 求解器")
        root.geometry(GEOMETRY)

        # 方程输入
        self.eq_frame = ttk.LabelFrame(root, text="方程（每行一个）")
        self.eq_frame.pack(fill="both", expand=True, padx=5, pady=5)
        self.eq_text = tk.Text(self.eq_frame, height=10)
        self.eq_text.pack(fill="both", expand=True, padx=5, pady=5)

        # 控制区
        ctrl = ttk.Frame(root)
        ctrl.pack(fill="x", padx=5, pady=5)

        ttk.Label(ctrl, text="目标变量（逗号分隔）:").grid(row=0, column=0, sticky="w")
        self.target_entry = ttk.Entry(ctrl, width=15)
        self.target_entry.grid(row=0, column=1, padx=5)

        ttk.Label(ctrl, text="允许符号（逗号分隔）:").grid(row=0, column=2, sticky="w")
        self.allowed_entry = ttk.Entry(ctrl, width=15)
        self.allowed_entry.grid(row=0, column=3, padx=5)

        ttk.Button(ctrl, text="求解", command=self.run_solve).grid(row=0, column=4, padx=10)

        # 输出区
        out_frame = ttk.LabelFrame(root, text="结果")
        out_frame.pack(fill="both", expand=True, padx=5, pady=5)
        self.out_text = tk.Text(out_frame, state="disabled")
        self.out_text.pack(fill="both", expand=True, padx=5, pady=5)
        scroll = ttk.Scrollbar(self.out_text)
        scroll.pack(side="right", fill="y")
        self.out_text.config(yscrollcommand=scroll.set)
        scroll.config(command=self.out_text.yview)

    def run_solve(self):
        # 清除之前的错误标记
        self.eq_text.tag_remove("error", "1.0", "end")
        self.eq_text.tag_configure("error", background="yellow", foreground="red")
        
        try:
            # 1. 解析方程
            eq_lines = self.eq_text.get("1.0", "end").strip().splitlines()
            equations = []
            error_lines = []  # 记录错误行号
            
            for line_num, line in enumerate(eq_lines, 1):
                line = line.strip()
                if not line:
                    continue
                if "=" not in line:
                    error_lines.append((line_num, f"方程缺少等号: {line}"))
                    continue
                try:
                    lhs_str, rhs_str = line.split("=", 1)
                    lhs = parse_expr_safe(lhs_str)
                    rhs = parse_expr_safe(rhs_str)
                    equations.append(sp.Eq(lhs, rhs))
                except Exception as e:
                    error_lines.append((line_num, str(e)))

            # 如果有错误，高亮显示并提示
            if error_lines:
                error_messages = []
                for line_num, error_msg in error_lines:
                    # 高亮错误行
                    start_pos = f"{line_num}.0"
                    end_pos = f"{line_num}.end"
                    self.eq_text.tag_add("error", start_pos, end_pos)
                    error_messages.append(f"第{line_num}行: {error_msg}")
                
                error_text = "\n".join(error_messages)
                messagebox.showerror("表达式错误", f"以下表达式不合法:\n\n{error_text}")
                return

            if not equations:
                raise ValueError("请至少输入一个方程")

            # 2. 解析变量
            target_str = self.target_entry.get().strip()
            allowed_str = self.allowed_entry.get().strip()
            if not target_str:
                raise ValueError("请指定目标变量")
            targets = [sp.Symbol(s.strip()) for s in target_str.split(",") if s.strip()]
            allowed_syms = {sp.Symbol(s.strip()) for s in allowed_str.split(",") if s.strip()}

            # 3. 求解并展示
            success, lines = solve_all(equations, targets, allowed_syms)
            self.out_text.config(state="normal")
            self.out_text.delete("1.0", "end")
            hdr = (f"--- 求解 {', '.join(str(t) for t in targets)}"
                   f"（允许符号：{', '.join(str(s) for s in allowed_syms)}） ---\n")
            self.out_text.insert("end", hdr)
            for ln in lines:
                self.out_text.insert("end", ln + "\n")
            self.out_text.config(state="disabled")
        except Exception as e:
            messagebox.showerror("错误", str(e))

# ---------- 主程序 ---------- #
if __name__ == "__main__":
    sp.init_printing()
    root = tk.Tk()
    EquationSolverGUI(root)
    root.mainloop()